// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#include <iostream>
#include <memory>

#include <cxxopts.hpp>

#include "coin2/app/driver.h"
#include "coin2/base/config/app_config.h"
#include "coin2/base/config/args.h"
#include "coin2/exchange/base/executor/sync_executor.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/feed.pb.h"
#include "coin2/strategy/strategy.h"

using namespace coin2::base::config;
using namespace coin2::app;
using namespace coin2::exchange::base::symbology;
using namespace coin2::exchange::feed;
using coin2::exchange::base::feed::FeedUpdate;

bool any = false;

class FeedDiscontinuityChecker : public coin2::strategy::IStrategy {
 public:
  explicit FeedDiscontinuityChecker(int64_t trade_gap, int64_t book_gap, int64_t start)
      : trade_gap_(trade_gap), book_gap_(book_gap), last_trade_(start), last_book_(start) {}

  void Init(::coin2::app::Driver* driver) override {
    auto ps = driver->feed()->GetProducts();
    CHECK_EQ_THROW(ps.size(), 1);
  }

  void onTradeFeed(const FeedUpdate& upd) override {
    int64_t ts = upd.timestamp();
    if (last_trade_ != 0 && ts - last_trade_ > trade_gap_) {
      printf(
          "trade gap: %10.4f secs : %ld ~ %ld (%s ~ %s)\n",
          static_cast<double>(ts - last_trade_) * 1e-9,
          last_trade_,
          ts,
          Iso8601FromTimestamp(last_trade_).c_str(),
          Iso8601FromTimestamp(ts).c_str());
      any = true;
    }
    last_trade_ = ts;
  }

  void onBookFeed(const FeedUpdate& upd) override {
    int64_t ts = upd.timestamp();
    if (last_book_ != 0 && ts - last_book_ > book_gap_) {
      printf(
          " book gap: %10.4f secs : %ld ~ %ld (%s ~ %s)\n",
          static_cast<double>(ts - last_book_) * 1e-9,
          last_book_,
          ts,
          Iso8601FromTimestamp(last_book_).c_str(),
          Iso8601FromTimestamp(ts).c_str());
      any = true;
    }
    last_book_ = ts;
  }

 private:
  int64_t trade_gap_;
  int64_t book_gap_;
  int64_t last_trade_;
  int64_t last_book_;
};

int main(int argc, char* argv[]) {
  // setup args
  cxxopts::Options opt(
      "Feed Discontinuity checker",
      "Check Feed Discontinuity Checker for (machine,date,mear,symbol)");
  opt.add_options()("machine", "", cxxopts::value<std::string>());
  opt.add_options()("mear", "", cxxopts::value<std::string>());
  opt.add_options()("symbol", "norm or feed_native", cxxopts::value<std::string>());
  opt.add_options()(
      "trade-gap",
      "ns",
      cxxopts::value<int64_t>()->default_value("20000000000"));  // 20 secs
  opt.add_options()(
      "book-gap",
      "ns",
      cxxopts::value<int64_t>()->default_value("1000000000"));  // 1 sec

  AddIntervalOptions(&opt);

  // parse args
  auto res = opt.parse(argc, argv);

  CHECK_GT_THROW(res.count("machine"), 0);
  CHECK_GT_THROW(res.count("symbol"), 0);
  {
    int cnt = res.count("start") + res.count("end") + res.count("duration");
    CHECK_EQ_THROW(cnt, 2);
  }

  auto machine = res["machine"].as<std::string>();
  auto mear = MarketExchangeApiRecipe::FromString(res["mear"].as<std::string>());
  auto symbol = res["symbol"].as<std::string>();
  auto trade_gap = res["trade-gap"].as<int64_t>();
  auto book_gap = res["book-gap"].as<int64_t>();

  // construct config
  DriverConfig app;
  {
    auto& exchange = (*app.mutable_feed()->mutable_exchanges())[mear.mea.String()];
    exchange.set_type(FeedSystemType::FEED_SYSTEM_TYPE_ARCHIVE);
    exchange.mutable_products()->add_norms(symbol);
    exchange.set_recipe(mear.recipe);

    auto& archive = *exchange.mutable_archive();
    archive.set_archive_type(ArchiveConfig_ArchiveType::ArchiveConfig_ArchiveType_FASTFEED);
    archive.set_machine(machine);
    ParseIntervalOptions(res, archive.mutable_interval());
  }

  // get begin_timestamp
  std::pair<int64_t, int64_t> interval;
  CHECK_EQ_THROW(app.feed().exchanges().size(), 1);
  for (auto& [id, sys] : app.feed().exchanges()) {
    (void)id;
    interval = FromIntervalConfig(sys.archive().interval());
  }

  // run
  FeedDiscontinuityChecker s(trade_gap, book_gap, interval.first);
  coin2::app::Driver driver(app, &s);
  driver.Run();

  auto ps = driver.feed()->GetProducts();

  if (any) {
    printf("%s : discontinuity found\n", ps[0]->absolute_norm().c_str());
    return 1;
  }

  printf("%s : clean\n", ps[0]->absolute_norm().c_str());
  return 0;
}
